home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Celestin Apprentice 7
/
Apprentice-Release7.iso
/
Source Code
/
Pascal
/
Applications
/
NIH Image 1.62b11
/
src
/
Edm.p
< prev
next >
Wrap
Text File
|
1996-12-16
|
18KB
|
703 lines
unit EDM;
{Euclidian distance maps, ultimate eroded points, and watershed segmentation}
interface
uses
Types, Memory, QuickDraw, QuickDrawText, Packages, Menus, Events, Fonts,
Scrap, ToolUtils, Resources, Errors, Palettes, StandardFile, Windows,
Controls, TextEdit, Files, Dialogs, TextUtils, Finder, MixedMode, Processes,
globals, Utilities, Graphics, Analysis;
procedure MakeEDM(item: integer);
implementation
type
Image16Data = packed array[0..maxImageSize] of integer;
Image16Ptr = ^Image16Data;
StartsArray = array[0..255] of LongInt;
function FindUtimatePoints(MaxEDM,item: LongInt; image2: ImageP;
LevelStart: StartsArray; xCoordinate, yCoordinate: Image16Ptr): boolean;
{
Finds peaks in the EDM that contain pixels equal to or greater than
all of their neighbors.
}
var
x, y, level, rowsize, offset, i, count: LongInt;
CoordOffset, xmax, ymax: LongInt;
NextTick: LongInt;
image: ImageP;
SetPixel: boolean;
begin
with info^ do begin
image := ImageP(PicbaseAddr);
BlockMove(image, image2, PixMapSize);
rowsize := BytesPerRow;
xmax := PixelsPerLine - 1;
ymax := nLines - 1;
NextTick := TickCount + 15;
for level := maxEDM - 1 downto 1 do begin
repeat
count := 0;
for i := 0 to Histogram[level] - 1 do begin
CoordOffset := LevelStart[level] + i;
x:= xCoordinate^[CoordOffset];
y:= yCoordinate^[CoordOffset];
offset := x + y * rowsize;
if image^[offset] <> 255 then begin
SetPixel := false;
if (x > 0) and (y > 0) then
if image^[offset - rowSize - 1] > level then
SetPixel := true;
if y > 0 then
if image^[offset - rowSize] > level then
SetPixel := true;
if (x < xmax) and (y > 0) then
if image^[offset - rowSize + 1] > level then
SetPixel := true;
if x < xmax then
if image^[offset + 1] > level then
SetPixel := true;
if (x < xmax) and (y < ymax) then
if image^[offset + rowSize + 1] > level then
SetPixel := true;
if y < ymax then
if image^[offset + rowSize] > level then
SetPixel := true;
if (x > 0) and (y < ymax) then
if image^[offset + rowSize - 1] > level then
SetPixel := true;
if x > 0 then
if image^[offset - 1] > level then
SetPixel := true;
if SetPixel then begin
image^[offset] := 255;
count := count + 1;
end;
end; {if}
end; {for i}
until count = 0;
if TickCount > NextTick then begin
ShowAnimatedWatch;
if CommandPeriod then begin
beep;
undo;
WhatToUndo := NothingToUndo;
FindUtimatePoints := false;
exit(FindUtimatePoints);
end;
NextTick := TickCount + 15;
end;
end; {for level}
if item = WatershedItem then begin
for i := 0 to info^.PixMapSize - 1 do
if (image^[i] > 0) and (image^[i] < 255) then
image2^[i] := 255;
BlockMove(image2, image, PixMapSize);
end else begin
for i := 0 to info^.PixMapSize - 1 do
if image^[i] = 255 then
image^[i] := 0;
end;
FindUtimatePoints := true;
end; {with}
end;
procedure DoWatershedSegmentation(MaxEDM: LongInt; image2: ImageP;
LevelStart: StartsArray; xCoordinate, yCoordinate: Image16Ptr;
var iterations: LongInt);
{
Assumes the current image contains an EDM and that the peaks in the EDM
(the ultimate eroded points) have been set to 255. The EDM is dilated from
these peaks, starting at the highest peak and working down. At each level,
the dilation is constrained to pixels whose values are at that level and
also constrained to prevent features from merging.
}
var
rowSize, NextTicks: LongInt;
level, x, y, offset, xmax, ymax: LongInt;
count, FirstCount: LongInt;
table: FateTable;
image: ImageP;
procedure CheckAbort;
begin
UpdatePicWindow;
if CommandPeriod then begin
beep;
undo;
WhatToUndo := NothingToUndo;
exit(DoWatershedSegmentation);
end;
NextTicks := TickCount + 30;
if OptionKeyDown then
wait(30);
end;
procedure MakeFateTable;
{Add pixel on 1st pass if bit 0 is set, on 2nd pass if bit 1 is}
{set, on 3rd pass if bit 2 is set, and on 4th pass if bit 3 is set.}
{E.g. '4' = add on 3rd pass, '3' = add on either 1st or 2nd pass,}
{'f' = add on any pass. There is a routine in 'user.p' that draws}
{all 256 neighborhoods.}
const
s999 = '01234567890123456789012345678901';
s000 = '0044004480cc80cc0000000080cc80cc';
s032 = '1000000080ff80ff1000000090ff90ff';
s064 = '00000000000000000000000000000000';
s096 = '1000000090ff90ff1000000090ff90ff';
s128 = '2266006600ff00ff0000000000ff00ff';
s160 = '13ff00ffffffffff33ff00ffffffffff';
s192 = '2266006600ff00ff0000000000ff00ff';
s224 = '33ff00ffffffffff33ff00fffffffff';
var
s: str255;
c: char;
i: LongInt;
begin
s := concat(s000, s032, s064, s096, s128, s160, s192, s224);
for i := 0 to 254 do begin
c := s[i + 1];
if c <= '9' then
table[i] := ord(c) - ord('0')
else
table[i] := 10 + ord(c) - ord('a')
end;
table[255] := 15; {f}
end;
procedure ProcessLevel(level, pass: LongInt);
var
index, x, y, i, CoordOffset, offset: LongInt;
begin
BlockMove(image, image2, info^.PixMapSize);
for i := 0 to Histogram[level] - 1 do begin
CoordOffset := LevelStart[level] + i;
x:= xCoordinate^[CoordOffset];
y:= yCoordinate^[CoordOffset];
offset := x + y * rowsize;
if image2^[offset] <> 255 then begin
index := 0;
if (x > 0) and (y > 0) then
if image2^[offset - rowSize - 1] = 255 then
index := bor(index, 1);
if y > 0 then
if image2^[offset - rowSize] = 255 then
index := bor(index, 2);
if (x < xmax) and (y > 0) then
if image2^[offset - rowSize + 1] = 255 then
index := bor(index, 4);
if x < xmax then
if image2^[offset + 1] = 255 then
index := bor(index, 8);
if (x < xmax) and (y < ymax) then
if image2^[offset + rowSize + 1] = 255 then
index := bor(index, 16);
if y < ymax then
if image2^[offset + rowSize] = 255 then
index := bor(index, 32);
if (x > 0) and (y < ymax) then
if image2^[offset + rowSize - 1] = 255 then
index := bor(index, 64);
if x > 0 then
if image2^[offset - 1] = 255 then
index := bor(index, 128);
case pass of
1: if band(table[index], 1) = 1 then begin
image^[offset] := 255;
count := count + 1;
end;
2: if band(table[index], 2) = 2 then begin
image^[offset] := 255;
count := count + 1;
end;
3: if band(table[index], 4) = 4 then begin
image^[offset] := 255;
count := count + 1;
end;
4: if band(table[index], 8) = 8 then begin
image^[offset] := 255;
count := count + 1;
end;
end; {case}
end; {if}
end; {for i}
end; {ProcessLevel}
procedure PostProcess;
var
i: LongInt;
begin
for i := 0 to info^.PixMapSize - 1 do
if image^[i] < 255 then
image^[i] := 0
end;
begin
with info^ do begin
rowSize := BytesPerRow;
MakeFateTable;
image := ImageP(PicbaseAddr);
xmax := PixelsPerLine - 1;
ymax := nLines - 1;
NextTicks := TickCount;
iterations := 0;
for level := maxEDM - 1 downto 1 do begin
repeat
count := 0;
ProcessLevel(level, 1);
ProcessLevel(level, 3);
ProcessLevel(level, 2);
ProcessLevel(level, 4);
iterations := iterations + 1;
until count = 0;
if TickCount > NextTicks then
CheckAbort;
end; {for level}
PostProcess;
UpdatePicWindow;
end;
end;
procedure SmoothEDM(image2: ImageP);
var
x, y, rowsize, offset, sum: LongInt;
xmax, ymax: LongInt;
image: ImageP;
begin
with info^ do begin
image := ImageP(PicbaseAddr);
BlockMove(image, image2, PixMapSize);
rowsize := BytesPerRow;
xmax := PixelsPerLine - 1;
ymax := nLines - 1;
for y := 0 to nLines - 1 do begin
for x := 0 to PixelsPerLine - 1 do begin
offset := x + y * rowsize;
if image2^[offset] <> 1 then begin
sum := image2^[offset] * 2;
if (x > 0) and (y > 0) then
sum := sum + image2^[offset - rowSize - 1];
if y > 0 then
sum := sum + image2^[offset - rowSize];
if (x < xmax) and (y > 0) then
sum := sum + image2^[offset - rowSize + 1];
if x < xmax then
sum := sum + image2^[offset + 1];
if (x < xmax) and (y < ymax) then
sum := sum + image2^[offset + rowSize + 1];
if y < ymax then
sum := sum + image2^[offset + rowSize];
if (x > 0) and (y < ymax) then
sum := sum + image2^[offset + rowSize - 1];
if x > 0 then
sum := sum + image2^[offset - 1];
image^[offset] := sum div 10;
end;
end; {for x}
end; {for y}
end; {with}
end;
procedure MakeEDM(item: integer);
{
Converts a binary image into a grayscale Euclidian distance
map (EDM). Each foreground (black) pixel in the binary image
is assigned a value equal to its distance from the nearest
background (white) pixel. Uses the two pass EDM algorithm
from the "Image Pricessing Handbook" by John Russ.
}
const
one = 41;
sqrt2 = 58; {~41*sqrt(2)}
sqrt5 = 92; {~41*sqrt(5)}
var
x, y, xmax, ymax, i, MaxEDM: LongInt;
StartTicks, NextTicks, offset, rowsize: LongInt;
iterations: LongInt;
PointsOK: boolean;
image16: Image16Ptr;
image2: ImageP;
str: str255;
LevelStart: StartsArray;
xCoordinate, yCoordinate: Image16Ptr;
function ConvertToIntegers:boolean;
var
x, y, offset: LongInt;
begin
with info^ do begin
image16 := Image16Ptr(NewPtr(rowsize * nLines * SizeOf(integer)));
if image16 = nil then begin
ConvertToIntegers := false;
exit(ConvertToIntegers);
end;
for y := 0 to nLines - 1 do
for x := 0 to PixelsPerLine - 1 do begin
offset := x + y * rowsize;
image16^[offset] := ImageP(PicBaseAddr)^[offset] * one;
end;
ConvertToIntegers := true;
end;
end;
procedure ConvertToBytes;
var
x, y, v, round, offset: LongInt;
begin
round := one div 2;
MaxEDM := 0;
with info^ do begin
for y := 0 to nLines - 1 do
for x := 0 to PixelsPerLine - 1 do begin
offset := x + y * rowsize;
v := (image16^[offset] + round) div one;
if v > 255 then
v := 255;
if v > maxEDM then
maxEDM := v;
ImageP(PicBaseAddr)^[offset] := v;
end;
end;
end;
procedure SetEdgeValue;
var
min, inc, v: LongInt;
r1, r2, r3, r4, r5, offimage: LongInt;
begin
r1 := offset - rowsize - rowsize - 2;
r2 := r1 + rowsize;
r3 := r2 + rowsize;
r4 := r3 + rowsize;
r5 := r4 + rowsize;
min := 32767;
offimage := image16^[r3 + 2];
if y < 2 then
v := offImage + one
else
v := image16^[r2 + 2] + one;
if v < min then
min := v;
if x < 2 then
v := offImage + one
else
v := image16^[r3 + 1] + one;
if v < min then
min := v;
if x > xmax then
v := offImage + one
else
v := image16^[r3 + 3] + one;
if v < min then
min := v;
if y > ymax then
v := offImage + one
else
v := image16^[r4 + 2] + one;
if v < min then
min := v;
if (x < 2) or (y < 2) then
v := offImage + sqrt2
else
v := image16^[r2 + 1] + sqrt2;
if v < min then
min := v;
if (x > xmax) or (y < 2) then
v := offImage + sqrt2
else
v := image16^[r2 + 3] + sqrt2;
if v < min then
min := v;
if (x < 2) or (y > ymax) then
v := offImage + sqrt2
else
v := image16^[r4 + 1] + sqrt2;
if v < min then
min := v;
if (x > xmax) or (y > ymax) then
v := offImage + sqrt2
else
v := image16^[r4 + 3] + sqrt2;
if v < min then
min := v;
if (x < 2) or (y < 2) then
v := offImage + sqrt5
else
v := image16^[r1 + 1] + sqrt5;
if v < min then
min := v;
if (x > xmax) or (y < 2) then
v := offImage + sqrt5
else
v := image16^[r1 + 3] + sqrt5;
if v < min then
min := v;
if (x > xmax) or (y < 2) then
v := offImage + sqrt5
else
v := image16^[r2 + 4] + sqrt5;
if v < min then
min := v;
if (x > xmax) or (y > ymax) then
v := offImage + sqrt5
else
v := image16^[r4 + 4] + sqrt5;
if v < min then
min := v;
if (x > xmax) or (y > ymax) then
v := offImage + sqrt5
else
v := image16^[r5 + 3] + sqrt5;
if v < min then
min := v;
if (x < 2) or (y > ymax) then
v := offImage + sqrt5
else
v := image16^[r5 + 1] + sqrt5;
if v < min then
min := v;
if (x < 2) or (y > ymax) then
v := offImage + sqrt5
else
v := image16^[r4] + sqrt5;
if v < min then
min := v;
if (x < 2) or (y < 2) then
v := offImage + sqrt5
else
v := image16^[r2] + sqrt5;
if v < min then
min := v;
image16^[offset] := min;
end; {SetEdgeValue}
procedure SetValue;
var
min, inc, v: LongInt;
r1, r2, r3, r4, r5: LongInt;
begin
r1 := offset - rowsize - rowsize - 2;
r2 := r1 + rowsize;
r3 := r2 + rowsize;
r4 := r3 + rowsize;
r5 := r4 + rowsize;
min := 32767;
v := image16^[r2 + 2] + one;
if v < min then
min := v;
v := image16^[r3 + 1] + one;
if v < min then
min := v;
v := image16^[r3 + 3] + one;
if v < min then
min := v;
v := image16^[r4 + 2] + one;
if v < min then
min := v;
v := image16^[r2 + 1] + sqrt2;
if v < min then
min := v;
v := image16^[r2 + 3] + sqrt2;
if v < min then
min := v;
v := image16^[r4 + 1] + sqrt2;
if v < min then
min := v;
v := image16^[r4 + 3] + sqrt2;
if v < min then
min := v;
v := image16^[r1 + 1] + sqrt5;
if v < min then
min := v;
v := image16^[r1 + 3] + sqrt5;
if v < min then
min := v;
v := image16^[r2 + 4] + sqrt5;
if v < min then
min := v;
v := image16^[r4 + 4] + sqrt5;
if v < min then
min := v;
v := image16^[r5 + 3] + sqrt5;
if v < min then
min := v;
v := image16^[r5 + 1] + sqrt5;
if v < min then
min := v;
v := image16^[r4] + sqrt5;
if v < min then
min := v;
v := image16^[r2] + sqrt5;
if v < min then
min := v;
image16^[offset] := min;
end; {SetValue}
procedure CheckAbort;
begin
ShowAnimatedWatch;
if CommandPeriod then begin
beep;
DisposePtr(ptr(image16));
exit(MakeEDM);
end;
NextTicks := TickCount + 15;
end;
procedure MakeCoordinateArrays;
{Generates the XY coordinate arrays that allow pixels at each level to
be accesed directly without searching through the entire image. This
method, suggested by Stein Roervik (steinr@kjemi.unit.no), greatly
speeds up the watershed segmentation routine.}
var
i, x, y, rowsize, offset, v, ArraySize: LongInt;
LevelOffset: array[0..255] of LongInt;
image: ImageP;
begin
with info^ do begin
RoiRect := PicRect;
GetRectHistogram;
ArraySize := 0;
for i := 1 to maxEDM - 1 do
ArraySize := ArraySize + histogram[i];
xCoordinate := Image16Ptr(NewPtr(ArraySize * SizeOf(integer)));
yCoordinate := Image16Ptr(NewPtr(ArraySize * SizeOf(integer)));
if (xCoordinate = nil) or (yCoordinate = nil) then begin
if xCoordinate <> nil then
DisposePtr(ptr(xCoordinate));
DisposePtr(ptr(image2));
PutError('Out of memory');
undo;
WhatToUndo := NothingToUndo;
exit(MakeEDM);
end;
image := ImageP(PicbaseAddr);
offset := 0;
for i := 0 to 255 do begin
LevelStart[i] := offset;
if (i >= 1) and (i <= (MaxEDM - 1)) then
offset := offset + histogram[i];
end;
for I := 0 to 255 do
LevelOffset[i] := 0;
rowsize := BytesPerRow;
for y := 0 to nLines - 1 do begin
for x := 0 to PixelsPerLine - 1 do begin
v := image^[x + y * rowsize];
if (v >= 1) and (v <= (MaxEDM - 1)) then begin
offset := LevelStart[v] + LevelOffset[v];
xCoordinate^[offset] := x;
yCoordinate^[offset] := y;
LevelOffset[v] := LevelOffset[v] + 1;
end;
end; {for x}
end; {for y}
end; {with}
end;
begin
with info^ do begin
ShowWatch;
KillRoi;
rowsize := BytesPerRow;
xmax := PixelsPerLine - 3;
ymax := nLines - 3;
StartTicks := TickCount;
NextTicks := TickCount;
SetupUndo;
WhatToUndo := UndoFilter;
if not ConvertToIntegers then begin
AbortMacro;
PutError('Out of Memory');
exit(MakeEDM);
end;
for y := 0 to nLines - 1 do begin
for x := 0 to PixelsPerLine - 1 do begin
offset := x + y * rowsize;
if image16^[offset] > 0.0 then begin
if (x < 2) or (x > xmax) or (y < 2) or (y > ymax) then
SetEdgeValue
else
SetValue;
end;
end;
if TickCount > NextTicks then
CheckAbort;
end;
for y := nLines - 1 downto 0 do begin
for x := PixelsPerLine - 1 downto 0 do begin
offset := x + y * rowsize;
if image16^[offset] > 0.0 then begin
if (x < 2) or (x > xmax) or (y < 2) or (y > ymax) then
SetEdgeValue
else
SetValue;
end;
end;
if TickCount > NextTicks then
CheckAbort;
end;
ConvertToBytes;
DisposePtr(ptr(image16));
if (item = UltimateItem) or (item = WatershedItem) then begin
image2 := ImageP(NewPtr(PixMapSize));
if image2 = nil then
exit(MakeEDM);
if ((item = WatershedItem) and not OptionKeyWasDown) or ((item = UltimateItem) and OptionKeyWasDown) then
SmoothEDM(image2);
MakeCoordinateArrays;
PointsOK := FindUtimatePoints(MaxEDM, item, image2, LevelStart, xCoordinate, yCoordinate);
end;
if (item = WatershedItem) and PointsOK then begin
DoWatershedSegmentation(MaxEDM, image2, LevelStart, xCoordinate, yCoordinate, iterations);
str := StringOf(MaxEDM - 1:1, ' levels', crStr, iterations/(MaxEDM - 1):1:2, ' iterations/level');
end else
str := '';
if (item = UltimateItem) or (item = WatershedItem) then begin
DisposePtr(ptr(xCoordinate));
DisposePtr(ptr(yCoordinate));
DisposePtr(ptr(image2));
end;
ShowTime(StartTicks, PicRect, str);
changes := true;
UpdatePicWindow;
end; {with}
end;
end.